#include <iostream>
#include <stdio.h>
//#include "config.h"

#include "SysPlusPlus/syscall.h"
#include "SysPlusPlus/syslib.h"
#include "SysPlusPlus/Tools.h"
#include "ComPlusPlus/Poll.h"
#include "ComPlusPlus/SocketUnix.h"
#include "SysPlusPlus/GenCfg.h"
#include "SysPlusPlus/ComException.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <stdio.h>
#include <signal.h>
#include <netdb.h>


/**
 * Internal socket creation on OS level
 **/
void compp::SocketUnix::NewUnixSocket(enum UDSTYPE T) {

int ty;
  STYP = T;
  if ( T == STREAM ) {
	ty = SOCK_STREAM;
  } else {
	ty = SOCK_DGRAM;
  }

  this->fd = syspp::Call::Socket ( AF_UNIX, ty, 0 );
  if ( this->fd == -1 ) {
      throw syspp::ComException("Panic: Cannot create socket!");
  }
  IsOpen=false;
  FdClosed=false;
    
}

/**
 *  Destructor. Shuts down the socket, if the socket was attached
 * with the AttachFD Method.
 */
compp::SocketUnix::~SocketUnix() {

  if ( this->fd == -1 )
  	return;	
  if ( this->FD_IsAttached() ) {
      this->Close ();
      unlink (ownAddress.c_str());
  }
}


/**
 *  Constructor.
 * Defaults are applied according to compp::GetComm 
 */
compp::SocketUnix::SocketUnix(enum UDSTYPE T, unsigned int buffsz) : Socket (buffsz) {

  NewUnixSocket(T);
  
}

/**
 *  Constructor.
 * Defaults are applied according to compp::GetComm 
 */
compp::SocketUnix::SocketUnix(int lfd, sockaddr_un *u, size_t l,unsigned int readaheadbufsize) : Socket (readaheadbufsize) {

  this->fd = lfd;  
  this->IsOpen = true;
  this->FdClosed = false;

  ownAddress = lfd;
}



/**
  Hard "Close" of the socket. This could lead to data loss.
  !!!!!!!!!!!!!! Handle with care!!!!!!!!!!!!!!!!
*/
void compp::SocketUnix::Close() {

  if ( this->fd == -1 ) {
    return ;
  }
  syspp::Call::Close ( this->fd  );
  this->fd = -1;

  IsOpen=false;
  FdClosed=true;
}


/**
 * Establishes the class on host:port and applies 
 * defaults from compp::GenCfg
 * After that carries out bind and listen.
 * Calling this constructor, one creates a working server socket already.
 * Only the Accept method must be used to get a usable clients server connection 
 */
compp::SocketUnix::SocketUnix(const std::string  & fn, enum UDSTYPE T, unsigned int buffsz) : Socket (buffsz) {

  NewUnixSocket(T);
  this->Bind ( fn );

}


/**
 * 
 *  works only on STREAM UDS
 */    
void compp::SocketUnix::Listen ( const int backlog ) {

  if ( STYP != STREAM ) {
	throw syspp::ComException ( "Cannot listen on DGRAM socket" );
  }

  if ( syspp::Call::Listen( this->fd, backlog) == -1) {
	throw syspp::ComException ( "Cannot listen on STREAM socket" );
        perror("listen");
  }

}

/**
 * Binds on string:port
 * 
 */
void compp::SocketUnix::Bind ( const std::string & fn ) {

  struct  sockaddr_un clntaddr;

  clntaddr.sun_family = AF_UNIX;
  strcpy(clntaddr.sun_path, fn.c_str() );
  
  if (bind(fd, (const sockaddr*)&clntaddr, sizeof(clntaddr)) < 0) {
    close(fd);
    perror("client: bind");
  }

  ownAddress = fn;
}

/**
 * Connect Socket to addr:port
*/
bool compp::SocketUnix::Connect( const std::string &fn ){	

  if ( STYP != STREAM ) {
	throw syspp::ComException ( "Cannot connect with DGRAM socket" );
  }
    
  struct sockaddr_un un;

  un.sun_family = AF_UNIX;
  strcpy(un.sun_path, fn.c_str() );

  if ( -1 == syspp::Call::Connect ( this->fd, ( const sockaddr*) &un, sizeof ( un ) ) ) {
    return false;
  }
 this->IsOpen=true;	
 this->FdClosed =false;
 return true;
}   

/**
 * Compatibility with Tcp Sockets. Just returns this
*/


compp::SocketUnix* compp::SocketUnix::Accept ( void ) {

  if ( STYP != STREAM ) {
	throw syspp::ComException ( "Cannot accept with DGRAM socket" );
  }

  struct sockaddr_un un;
  int sz = sizeof ( un );
  int lfd = syspp::Call::Accept(fd, (struct sockaddr *) &un, &sz);
 
  SocketUnix  *retval = new compp::SocketUnix ( lfd, &un, sizeof ( un ), ReadAheadBufferTotalSize );
  retval->SetReadAhead ( this->ReadAhead );
  this->IsOpen=true;	

  return retval;
}

/**
 * Sends len bytes to UDS path.
 * Returns the number of bytes sent or throws ComException.
 * */
int compp::SocketUnix::Sendto(const void *buf, size_t len, const std::string &fn){     

  if ( STYP != DATAGRAM ) {
	throw syspp::ComException ( "Cannot Sendto with STREAM socket" );
  }
    struct sockaddr_un ad; 
    int ret;
    
	_MkSockaddrUnix( fn, &ad);

 	ret = syspp::Call::Sendto( this->fd, buf, len, 0,(const struct sockaddr *) &ad, (int) sizeof ( ad));
	if ( ret == -1 ) {
		throw syspp::ComException( "Error in SendTo");
	}
	return ret;
}


/**
 * Receives maximum len bytes and stores the in buf. Flags can be set.
 * Sender is stored in from/port. From is converted to a std::string
 * for convenience.
 */
int compp::SocketUnix::Recvfrom(void *buf, int len, int flags, std::string &path){

  if ( STYP != DATAGRAM ) {
	throw syspp::ComException ( "Cannot Recvfrom STREAM socket" );
  }

	struct sockaddr_un fromsa;
	int ret;
	int i = sizeof ( fromsa);
        memset ( (void *) &fromsa, 0, sizeof ( fromsa) );
	ret = syspp::Call::Recvfrom(this->fd, buf, len, flags, (struct sockaddr*) &fromsa, &i );
        path = fromsa.sun_path;

        if ( ret == -1 && errno != EAGAIN ) {
	  return -1;
        }

	return ret;
}


/**
 * Creates soc addr from pathname
 **/
void compp::SocketUnix::_MkSockaddrUnix( const std::string &fn,  struct sockaddr_un *ad ) {
 
  memset( ad, 0, sizeof(struct sockaddr_un));
  ad->sun_family = AF_UNIX;
  strcpy(ad->sun_path, fn.c_str()); 

}   

void compp::SocketUnix::Shutdown (int method )  {

  shutdown ( fd, method );

}
